
/*-----------------------------------------------------------------------
 * Copyright (C) 2001 Green Light District Team, Utrecht University 
 * Copyright of the TC1 algorithm (C) Marco Wiering, Utrecht University
 *
 * This program (Green Light District) is free software.
 * You may redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by
 * the Free Software Foundation (version 2 or later).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * See the documentation of Green Light District for further information.
 *------------------------------------------------------------------------*/

package gld.algo.tlc;

import gld.*;
import gld.sim.*;
import gld.algo.tlc.*;
import gld.infra.*;
import gld.utils.*;
import gld.xml.*;

import java.io.IOException;
import java.util.*;
import java.awt.Point;

/**
 *
 * This controller will decide it's Q values for the traffic lights according to the traffic situation on
 * the lane connected to the TrafficLight. It will learn how to alter it's outcome by reinforcement learning.
 *
 * @author Arne K, Jilles V
 * @version 1.0
 */
public class TC1TLC extends TLController implements Colearning, InstantiationAssistant
{	protected Infrastructure infrastructure;
	protected TrafficLight[][] tls;
	protected Node[] allnodes;
	protected int num_nodes;
	
	protected Vector count, p_table;
	protected float [][][][] q_table; //sign, pos, des, color (red=0, green=1)
	protected float [][][]   v_table;
	protected static float gamma=0.95f;				//Discount Factor; used to decrease the influence of previous V values, that's why: 0 < gamma < 1
	protected static float random_chance=0.01f;				//A random gain setting is chosen instead of the on the TLC dictates with this chance
	protected final static boolean red=false, green=true;
	protected final static int green_index=0, red_index=1;
	protected final static String shortXMLName="tlc-tc1";
	private Random random_number;
	/**
	 * The constructor for TL controllers
	 * @param The model being used.
	 */
 
	public TC1TLC( Infrastructure infra ) throws InfraException
	{	super(infra);
		Node[] nodes = infra.getAllNodes(); //Moet Edge zijn eigenlijk, alleen testSimModel knalt er dan op
		int num_nodes = nodes.length;
		
		count = new Vector();
		p_table = new Vector();
		
		int numSigns = infra.getAllInboundLanes().size();
		q_table = new float [numSigns][][][];
		v_table = new float [numSigns][][];
		int num_specialnodes = infra.getNumSpecialNodes();
		for (int i=0; i<nodes.length; i++)
		{
			Node n = nodes[i];
			Drivelane [] dls = n.getInboundLanes();
			for (int j=0; j<dls.length; j++)
			{
				Drivelane d = dls[j];
				Sign s = d.getSign();
				int id = s.getId();
				int num_pos_on_dl = d.getCompleteLength();
				q_table[id] = new float [num_pos_on_dl][][];
				v_table[id] = new float [num_pos_on_dl][];
				for (int k=0; k<num_pos_on_dl; k++)
				{
					q_table[id][k]=new float[num_specialnodes][];
					v_table[id][k]=new float[num_specialnodes];
					for (int l=0; l<q_table[id][k].length;l++)
					{
						q_table[id][k][l]=new float [2];
						q_table[id][k][l][0]=0.0f;
						q_table[id][k][l][1]=0.0f;
						v_table[id][k][l]=0.0f;
					}
				}
			}
		}	
		random_number = new Random();
	}

	
	/**
	* Calculates how every traffic light should be switched
	* Per node, per sign the waiting roadusers are passed and per each roaduser the gain is calculated.
	* @param The TLDecision is a tuple consisting of a traffic light and a reward (Q) value, for it to be green
	* @see gld.algo.tlc.TLDecision
	*/	
	public TLDecision[][] decideTLs()
	{
		int num_dec;
	    int num_tld = tld.length;
	    
	    //Determine wheter it should be random or not
	    boolean do_this_random = false;
	    if (random_number.nextFloat() < random_chance) do_this_random = true;
	    
	    for (int i=0;i<num_tld;i++) {
	    	num_dec = tld[i].length;
	    	for(int j=0;j<num_dec;j++) {
	    		Sign currenttl = tld[i][j].getTL();
	    		float gain=0;
	    		
	    		Drivelane currentlane = currenttl.getLane();
	    		int waitingsize = currentlane.getNumRoadusersWaiting();
	    		ListIterator queue = currentlane.getQueue().listIterator();
	    		
	    		if(!do_this_random) {
	    			for(; waitingsize>0; waitingsize--)
	    			{
	    				Roaduser ru = (Roaduser) queue.next();
	    				int pos = ru.getPosition();
	    				Node destination = ru.getDestNode();
		    			
	    				gain += q_table[currenttl.getId()][pos][destination.getId()][1] - q_table[currenttl.getId()][pos][destination.getId()][0];  //red - green	    			
	    			}
	    			float q = gain;
					if(trackNode!=-1)
					if(i==trackNode) {
						Drivelane currentlane2 = tld[i][j].getTL().getLane();
						boolean[] targets = currentlane2.getTargets();
						System.out.println("node: "+i+" light: "+j+" gain: "+q+" "+targets[0]+" "+targets[1]+" "+targets[2]+" "+currentlane2.getNumRoadusersWaiting());
					}
                }
                else gain = random_number.nextFloat();
	    		tld[i][j].setQValue(gain);
	    	}
	    }
	    return tld;
	}

	public void updateRoaduserMove(Roaduser ru, Drivelane prevlane, Sign prevsign, int prevpos, Drivelane dlanenow, Sign signnow, int posnow, Drivelane[] possiblelanes, Point[] ranges, Drivelane desired)
	{
		//When a roaduser leaves the city; this will 
		if(dlanenow == null || signnow == null)
		{
			dlanenow = prevlane;
			signnow = prevsign;
			posnow = -1;
			return;	// ?? is recalculation is not necessary ??
		}
		//This ordening is important for the execution of the algorithm!
		
		if(prevsign.getType()==Sign.TRAFFICLIGHT && (signnow.getType()==Sign.TRAFFICLIGHT || signnow.getType()==Sign.NO_SIGN)) {
			Node dest = ru.getDestNode();
			recalcP(prevsign, prevpos, dest, prevsign.mayDrive(), signnow, posnow);
			recalcQ(prevsign, prevpos, dest, prevsign.mayDrive(), signnow, posnow, possiblelanes, ranges);
			recalcV(prevsign, prevpos, dest);
		}
	}

	protected void recalcP(Sign tl, int pos, Node destination, boolean light, Sign tl_new, int pos_new)
	{
		//Update the count table, increase the current situation
		CountEntry currentsituation = new CountEntry(tl, pos, destination, light, tl_new, pos_new);
		int count_index = count.indexOf(currentsituation);
		if (count_index>=0) {
			CountEntry temp = (CountEntry) count.elementAt(count_index);
			temp.incrementValue();
			count.setElementAt(temp, count_index);
			currentsituation = temp;
		}
		else {
			count.add(currentsituation);
		}
		
		// Change the current chance
		int dest, source=0;
		dest = currentsituation.getValue();
		
		Enumeration enm = count.elements();
		while(enm.hasMoreElements()) {
			source += ((CountEntry) enm.nextElement()).sameSource(currentsituation);
		}
		
		PEntry currentchance = new PEntry(tl, pos, destination, light, tl_new, pos_new);
		if(source == 0) currentchance.setValue(0);
		else currentchance.setValue((float)dest/(float)source);
		
		int p_index = p_table.indexOf(currentchance);
		if(p_index>=0) p_table.setElementAt(currentchance, p_index);
		else p_table.add(currentchance);


		//Also check the other chances for updates

		int size = p_table.size()-1;
		for(; size>=0; size--)
		{
			PEntry P = (PEntry) p_table.elementAt(size);
			float pvalue = P.sameSource(currentsituation);
			if(pvalue > -1)
			{
				if(size != p_index) {
					P.setValue(pvalue * (float)source / ((float)source + 1));
				}
				p_table.setElementAt(P, size);
			}
		}
	}

	protected void recalcQ(Sign tl, int pos, Node destination, boolean light, Sign tl_new, int pos_new, Drivelane[] possiblelanes, Point[] ranges)
	{
		/*	Recalculate the Q values, only one PEntry has changed, meaning also only 1 QEntry has to change
		*/

		float newQvalue=0;
		Target[] targets = ownedTargets(tl, pos, destination, light);
		int R;
		float V=0;
		
		for(int target_index=0; target_index<targets.length; target_index++)
		{
			PEntry P = new PEntry(tl, pos, destination, light, targets[target_index].getTL(), targets[target_index].getP());			
			int p_index = p_table.indexOf(P);
			if(p_index>=0)
			{
				P = (PEntry) p_table.elementAt(p_index);
				R = rewardFunction(tl_new, pos_new, possiblelanes, ranges);
				try
				{
				V = v_table[targets[target_index].getTL().getId()][targets[target_index].getP()][destination.getId()];
				}
				catch (Exception e)
				{
					System.out.println("ERROR");
					System.out.println("tl: "+targets[target_index].getTL().getId());
					System.out.println("pos:"+targets[target_index].getP());
					System.out.println("des:"+destination.getId());
				}
				
				newQvalue += P.getValue() *(R + (gamma * V));
				}
		}

		q_table[tl.getId()][pos][destination.getId()][light?green_index:red_index]=newQvalue;
		
	}

	protected void recalcV(Sign tl, int pos, Node destination)
	{
		float newVvalue;
		float q_red = q_table[tl.getId()][pos][destination.getId()][red_index];
		float q_green = q_table[tl.getId()][pos][destination.getId()][green_index];
		int[] amount = count(tl, pos, destination);
		
		//See the green_index definitions above !!!!
		
		newVvalue = ((float)amount[green_index]/(float)(amount[green_index]+amount[red_index]))*q_green + ((float)amount[red_index]/(float)(amount[green_index]+amount[red_index]))*q_red;

		v_table[tl.getId()][pos][destination.getId()]=newVvalue;
	}

	/*
				==========================================================================
							Additional methods, used by the recalc methods 
				==========================================================================
	*/

	protected int[] count(Sign tl, int pos, Node destination)
	{
		int[] counters;
		counters = new int[2];
		
		//See the green_index definitions above !!!!
		counters[green_index] = 0;
		counters[red_index] = 0;
		
		//Calcs the number of entries in the table matching the given characteristics, and returns the count
		int psize = p_table.size()-1;
		for(; psize>=0; psize--)
		{
			PEntry candidate = (PEntry) p_table.elementAt(psize);
			if(candidate.tl.getId() == tl.getId() && candidate.pos == pos && candidate.destination.getId() ==destination.getId()) {
					if(candidate.light == green) {
						counters[green_index]++;
					}
					else {
						counters[red_index]++;
					}
			}
		}
		return counters;
	}
	
	protected int rewardFunction(Sign tl_new, int pos_new, Drivelane[] possiblelanes, Point[] ranges)
	{
		//Ok, the reward function is actually very simple; it searches for the tuple (tl_new, pos_new) in the given set
		int size = possiblelanes.length;
		
		for(int i=0; i<size; i++)	{
			if( possiblelanes[i].equals(tl_new.getLane()) ) {
				if(ranges[i].x < pos_new)	{
					if(ranges[i].y > pos_new)	{
						return 0;
					}
				}
			}
		}
		return 1;
	}
	
	protected Target[] ownedTargets(Sign tl, int pos, Node des, boolean light)
	{
		//This method will determine to which destinations you can go starting at this source represented in this QEntry
		
		CountEntry dummy = new CountEntry(tl, pos, des, light, tl, pos);
		Target[] ownedtargets;
		Vector candidate_targets;
		candidate_targets = new Vector();
		
		//Use the count table to sort this out, we need all Targets from 
		//Only the elements in the count table are used, other  just give a P
		
		Enumeration enm = count.elements();
		while(enm.hasMoreElements()) {
			CountEntry current_entry = (CountEntry) enm.nextElement();
			if(current_entry.sameSource(dummy) != 0) {				
				candidate_targets.addElement(new Target(current_entry.tl_new , current_entry.pos_new));
			}
		}
		ownedtargets = new Target[candidate_targets.size()];
		candidate_targets.copyInto(ownedtargets);
		return ownedtargets;
	}

	public float getVValue(Sign sign, Node des, int pos)
	{
		return v_table[sign.getId()][pos][des.getId()];
	}


	public float getColearnValue(Sign now, Sign sign, Node des, int pos)
	{
		return getVValue(sign,des,pos);
	}
	

	/*
				==========================================================================
					Internal Classes to provide a way to put entries into the tables 
				==========================================================================
	*/

	public class CountEntry implements XMLSerializable , TwoStageLoader
	{
		Sign tl;
		int pos;
		Node destination;
		boolean light;
		Sign tl_new;
		int pos_new;
		int value;
		TwoStageLoaderData loadData=new TwoStageLoaderData();
		String parentName="model.tlc";
		
		CountEntry(Sign _tl, int _pos, Node _destination, boolean _light, Sign _tl_new, int _pos_new) {
			tl = _tl;
			pos = _pos;
			destination = _destination;
			light = _light;
			tl_new = _tl_new;
			pos_new = _pos_new;
			value=1;
		}
		
		CountEntry ()
		{ // Empty constructor for loading
		}
		
		public void incrementValue() {
			value++;
		}
		
		public int getValue() {
			return value;
		}

		public boolean equals(Object other) {
			if(other != null && other instanceof CountEntry)
			{	CountEntry countnew = (CountEntry) other;
				if(!countnew.tl.equals(tl)) return false;
				if(countnew.pos!=pos) return false;
				if(!countnew.destination.equals(destination)) return false;
				if(countnew.light!=light) return false;
				if(!countnew.tl_new.equals(tl_new)) return false;
				if(countnew.pos_new!=pos_new) return false;
				return true;
			}
			return false;
		}

		public int sameSource(CountEntry other) {
			if(other.tl.equals(tl) && other.pos == pos && other.light==light && other.destination.equals(destination)) {
				return value;
			}
			else {
				return 0;
			}
		}
		
		// XMLSerializable implementation of CountEntry
		
		public void load (XMLElement myElement,XMLLoader loader) throws XMLTreeException,IOException,XMLInvalidInputException
		{	pos=myElement.getAttribute("pos").getIntValue();
		   	loadData.oldTlId=myElement.getAttribute("tl-id").getIntValue();
		   	loadData.destNodeId=myElement.getAttribute("destination").getIntValue();
		   	light=myElement.getAttribute("light").getBoolValue();
			loadData.newTlId=myElement.getAttribute("newtl-id").getIntValue();
			pos_new=myElement.getAttribute("new-pos").getIntValue();
			value=myElement.getAttribute("value").getIntValue(); 
		}

		public XMLElement saveSelf () throws XMLCannotSaveException
		{ 	XMLElement result=new XMLElement("count");
			result.addAttribute(new XMLAttribute("tl-id",tl.getId()));
			result.addAttribute(new XMLAttribute("pos",pos));
			result.addAttribute(new	XMLAttribute("destination",destination.getId()));
			result.addAttribute(new XMLAttribute("light",light));
			result.addAttribute(new XMLAttribute("newtl-id",tl_new.getId()));
			result.addAttribute(new XMLAttribute("new-pos",pos_new));
			result.addAttribute(new XMLAttribute("value",value));
	  		return result;
		}
  
		public void saveChilds (XMLSaver saver) throws XMLTreeException,IOException,XMLCannotSaveException
		{ 	// A count entry has no child objects
		}

		public String getXMLName ()
		{ 	return parentName+".count";
		}
		
		public void setParentName (String parentName)
		{	this.parentName=parentName; 
		}
				
		// TwoStageLoader implementation of CountEntry

		class TwoStageLoaderData 
		{ int oldTlId,newTlId,destNodeId;
		}
		
		public void loadSecondStage (Dictionary dictionaries)
		{ Dictionary laneDictionary=(Dictionary)(dictionaries.get("lane")),
		             nodeDictionary=(Dictionary)(dictionaries.get("node"));
		  tl=((Drivelane)(laneDictionary.get(
		      new Integer(loadData.oldTlId)))).getSign();
		  tl_new=((Drivelane)(laneDictionary.get(
		      new Integer(loadData.newTlId)))).getSign();
		  destination=(Node)(nodeDictionary.get(
		      new Integer(loadData.destNodeId)));
		}

	}
	
	public class PEntry implements XMLSerializable, TwoStageLoader
	{
		Sign tl;
		int pos;
		Node destination;
		boolean light;
		Sign tl_new;
		int pos_new;
		float value;
		TwoStageLoaderData loadData=new TwoStageLoaderData();
		String parentName="model.tlc";
		
		PEntry(Sign _tl, int _pos, Node _destination, boolean _light, Sign _tl_new, int _pos_new) {
			tl = _tl;
			pos = _pos;
			destination = _destination;
			light = _light;
			tl_new = _tl_new;
			pos_new = _pos_new;
			value=0;
		}
		
		PEntry ()
		{	// Empty constructor for loading
		}
		
		public void setValue(float v) {
			value = v;
		}
		
		public float getValue() {
			return value;
		}

		public boolean equals(Object other) {
			if(other != null && other instanceof PEntry)
			{
				PEntry pnew = (PEntry) other;
				if(!pnew.tl.equals(tl)) return false;
				if(pnew.pos!=pos) return false;
				if(!pnew.destination.equals(destination)) return false;
				if(pnew.light!=light) return false;
				if(!pnew.tl_new.equals(tl_new)) return false;
				if(pnew.pos_new!=pos_new) return false;
				return true;
			}
			return false;
		}

		public float sameSource(CountEntry other) {
			if(other.tl.equals(tl) && other.pos == pos && other.light==light && other.destination.equals(destination)) {
				return value;
			}
			else {
				return -1;
			}
		}
		
		// XMLSerializable implementation of PEntry
		
		public void load (XMLElement myElement,XMLLoader loader) throws XMLTreeException,IOException,XMLInvalidInputException
		{	pos=myElement.getAttribute("pos").getIntValue();
		   	loadData.oldTlId=myElement.getAttribute("tl-id").getIntValue();
			loadData.destNodeId=myElement.getAttribute("destination").getIntValue();
		   	light=myElement.getAttribute("light").getBoolValue();
			loadData.newTlId=myElement.getAttribute("newtl-id").getIntValue();
			pos_new=myElement.getAttribute("new-pos").getIntValue();
			value=myElement.getAttribute("value").getFloatValue(); 
		}

		public XMLElement saveSelf () throws XMLCannotSaveException
		{ 	XMLElement result=new XMLElement("pval");
			result.addAttribute(new XMLAttribute("tl-id",tl.getId()));
			result.addAttribute(new XMLAttribute("pos",pos));
			result.addAttribute(new	XMLAttribute("destination",destination.getId()));
			result.addAttribute(new XMLAttribute("light",light));
			result.addAttribute(new XMLAttribute("newtl-id",tl_new.getId()));
			result.addAttribute(new XMLAttribute("new-pos",pos_new));
			result.addAttribute(new XMLAttribute("value",value));
	  		return result;
		}
		
		public void saveChilds (XMLSaver saver) throws XMLTreeException,IOException,XMLCannotSaveException
		{ 	// A PEntry has no child objects
		}
		
		public void setParentName (String parentName)
		{	this.parentName=parentName; 
		}
  
		public String getXMLName ()
		{ 	return parentName+".pval";
		}
				
		// TwoStageLoader implementation of PEntry

		class TwoStageLoaderData 
		{ 	int oldTlId,newTlId,destNodeId;
		}
		
		public void loadSecondStage (Dictionary dictionaries)
		{ 	Dictionary laneDictionary=(Dictionary)(dictionaries.get("lane")),
           		     	nodeDictionary=(Dictionary)(dictionaries.get("node"));
		  	tl=((Drivelane)(laneDictionary.get(
		       		new Integer(loadData.oldTlId)))).getSign();
		  	tl_new=((Drivelane)(laneDictionary.get(
		        	new Integer(loadData.newTlId)))).getSign();
		  	destination=(Node)(nodeDictionary.get(
		        	new Integer(loadData.destNodeId)));
		}
		
	}	
	
	protected class Target implements XMLSerializable , TwoStageLoader
	{
		Sign tl;
		int pos;
		TwoStageLoaderData loadData=new TwoStageLoaderData();
		String parentName="model.tlc";
		
		Target(Sign _tl, int _pos) {
			tl = _tl;
			pos = _pos;
		}
		
		Target ()
		{ // Empty constructor for loading
		}
		
		public Sign getTL() {
			return tl;
		}
		
		public int getP() {
			return pos;
		}

		public boolean equals(Object other) {
			if(other != null && other instanceof Target)
			{
				Target qnew = (Target) other;
				if(!qnew.tl.equals(tl)) return false;
				if(qnew.pos!=pos) return false;
				return true;
			}
			return false;
		}
		
		// XMLSerializable implementation of Target
		
		public void load (XMLElement myElement,XMLLoader loader) throws XMLTreeException,IOException,XMLInvalidInputException
		{	pos=myElement.getAttribute("pos").getIntValue();
		   loadData.tlId=myElement.getAttribute("tl-id").getIntValue();
		}
		
		public XMLElement saveSelf () throws XMLCannotSaveException
		{ 	XMLElement result=new XMLElement("target");
			result.addAttribute(new XMLAttribute("tl-id",tl.getId()));
			result.addAttribute(new XMLAttribute("pos",pos));
	  		return result;
		}
  
		public void saveChilds (XMLSaver saver) throws XMLTreeException,IOException,XMLCannotSaveException
		{ 	// A Target has no child objects
		}

		public String getXMLName ()
		{ 	return parentName+".target";
		}
		
		public void setParentName (String parentName)
		{	this.parentName=parentName;
		}
				
		// TwoStageLoader implementation of Target

		class TwoStageLoaderData 
		{ 	int tlId;
		}
		
		public void loadSecondStage (Dictionary dictionaries) throws XMLInvalidInputException,XMLTreeException
		{ 	Dictionary laneDictionary=(Dictionary)(dictionaries.get("lane"));
		  	tl=((Drivelane)(laneDictionary.get(
		                 new Integer(loadData.tlId)))).getSign();
		}
		
	}		
	
	
	public void showSettings(Controller c)
	{
		String[] descs = {"Gamma (discount factor)", "Random decision chance"};
		float[] floats = {gamma, random_chance};
		TLCSettings settings = new TLCSettings(descs, null, floats);
				
		settings = doSettingsDialog(c, settings);
		gamma = settings.floats[0];
		random_chance = settings.floats[1];
	}
	
	// XMLSerializable, SecondStageLoader and InstantiationAssistant implementation
	
	public void load (XMLElement myElement,XMLLoader loader) throws XMLTreeException,IOException,XMLInvalidInputException
	{	super.load(myElement,loader);
		gamma=myElement.getAttribute("gamma").getFloatValue();
		random_chance=myElement.getAttribute("random-chance").getFloatValue();
		q_table=(float[][][][])XMLArray.loadArray(this,loader);
		v_table=(float[][][])XMLArray.loadArray(this,loader);
		count=(Vector)XMLArray.loadArray(this,loader,this);
		p_table=(Vector)XMLArray.loadArray(this,loader,this);
	}

	public XMLElement saveSelf () throws XMLCannotSaveException
	{ 	XMLElement result=super.saveSelf();
		result.setName(shortXMLName);
		result.addAttribute(new XMLAttribute ("random-chance",random_chance));
		result.addAttribute(new XMLAttribute ("gamma",gamma));
	  	return result;
	}
  
	public void saveChilds (XMLSaver saver) throws XMLTreeException,IOException,XMLCannotSaveException
	{	super.saveChilds(saver);
		XMLArray.saveArray(q_table,this,saver,"q-table");
		XMLArray.saveArray(v_table,this,saver,"v-table");
		XMLArray.saveArray(count,this,saver,"counts");
		XMLArray.saveArray(p_table,this,saver,"p-table");
	}

	public String getXMLName ()
	{ 	return "model."+shortXMLName;
	}
		
	public void loadSecondStage (Dictionary dictionaries) throws XMLInvalidInputException,XMLTreeException
	{ 	XMLUtils.loadSecondStage(count.elements(),dictionaries);
		XMLUtils.loadSecondStage(p_table.elements(),dictionaries);
		System.out.println("TC1 second stage load finished.");			
	}
	
	public boolean canCreateInstance (Class request)
	{ 	return CountEntry.class.equals(request) ||
	        	PEntry.class.equals(request);
	}
	
	public Object createInstance (Class request) throws 
	      ClassNotFoundException,InstantiationException,IllegalAccessException
	{ 	if (CountEntry.class.equals(request))
		{ return new CountEntry();
		}
		else if ( PEntry.class.equals(request))
		{ return new PEntry();
		}
		else
		{ throw new ClassNotFoundException
		  ("TC1 IntstantiationAssistant cannot make instances of "+
		   request);
		}
	}	
	
}
